package editor;

import model.Project;
import model.code.CodeFile;
import model.code.Directory;
import model.code.Document;
import model.code.ProjectFile;
import util.Triple;

import javax.swing.*;
import javax.swing.tree.*;
import java.awt.*;
import java.awt.event.*;
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.TreeMap;

/**
 * This panel displays the project tree and allows the user to perform various operations on files.
 *
 * @author Neil Dickson
 */
public class ProjectPanel extends JComponent implements MouseListener {
	private PwnIDE editor;

	private	Project	project;

	private JTree	tree;
	private static final DefaultMutableTreeNode NO_PROJECT_TREE = new DefaultMutableTreeNode("(No Project Open)");

	public static final Color	BACKGROUND_COLOUR	= new Color(0,0,0,255);
	public static final Color	SELECTION_COLOUR	= new Color(0,40,90);

	public ProjectPanel(PwnIDE editor, Project project) {
		this.editor = editor;
		setLayout(new FlowLayout(FlowLayout.LEFT));
		setProject(project);
		setFocusable(false);
	}

	public static DefaultMutableTreeNode getTree(Directory directory) {
		DefaultMutableTreeNode root = new DefaultMutableTreeNode(directory,true);
		Collection<Directory> directories = directory.getDirectories().values();
		for (Directory dir : directories) {
			root.add(getTree(dir));
		}
		Collection<Document> documents = directory.getDocuments().values();
		for (Document doc : documents) {
			root.add(new DefaultMutableTreeNode(doc,false));
		}
		Collection<CodeFile> codeFiles = directory.getCodeFiles().values();
		for (CodeFile code : codeFiles) {
			root.add(new DefaultMutableTreeNode(code,false));
		}
		return root;
	}

	public void paintComponent(Graphics g) {
		g.setColor(getBackground());
		g.fillRect(0,0,getWidth(),getHeight());
	}

	public Dimension getMinimumSize() {
		return new Dimension(tree.getWidth()+tree.getX(),tree.getHeight()+tree.getY());
	}

	public void setProject(Project project) {
		this.project = project;
		if (tree!=null) {
			remove(tree);
		}
		if (project!=null) {
			tree = new JTree(getTree(project.getDirectory()));
			for (int i=0;i<tree.getRowCount();++i) {
				Object o = ((DefaultMutableTreeNode)tree.getPathForRow(i).getLastPathComponent()).getUserObject();
				if (o!=null && o instanceof Directory) {
					Directory dir = (Directory)o;
					if (dir.isExpanded()) {
						tree.expandRow(i);
					}
					else {
						tree.collapseRow(i);
					}
				}
			}
		}
		else {
			tree = new JTree(NO_PROJECT_TREE);
		}
		tree.setCellRenderer(new TreeRenderer());
		tree.setRootVisible(true);
		tree.setEditable(false);
		tree.setDragEnabled(true);
		tree.addMouseListener(this);
		tree.addComponentListener(new ComponentAdapter(){
			public void componentResized(ComponentEvent e) {
				setSize(tree.getX()+tree.getWidth(),tree.getY()+tree.getHeight());
			}
		});
		tree.setFocusable(false);
		add(tree);
		invalidate();
		validate();
	}

	public void mouseClicked(MouseEvent e) {
		if (e.getButton()==MouseEvent.BUTTON1) {
			if (e.getClickCount()==2) {
				Point pt = e.getPoint();
				TreePath path = tree.getPathForLocation(pt.x,pt.y);
				if (path!=null) {
					DefaultMutableTreeNode node = (DefaultMutableTreeNode) path.getLastPathComponent();
					Object file = node.getUserObject();
					if (!(file instanceof Directory)) {
						if (file instanceof CodeFile) {
							PwnIDE.ui.openFile((CodeFile)file);
						}
						else {
							ErrorHandler.showNotImplementedMessage("Sorry, only opening of code files works at the moment.");
						}
					}
				}
			}
		}
		else if (e.getButton()==MouseEvent.BUTTON3 && project!=null) {
			Point pt = e.getPoint();
			TreePath path = tree.getPathForLocation(pt.x,pt.y);
			if (path!=null) {
				DefaultMutableTreeNode node = (DefaultMutableTreeNode) path.getLastPathComponent();
				Object file = node.getUserObject();
				final String initialDirectory = (file instanceof Directory) ? ((Directory)file).getPath() : ((Directory)((DefaultMutableTreeNode)node.getParent()).getUserObject()).getPath();
				JPopupMenu menu = new JPopupMenu();
				JMenuItem newFileItem = menu.add("Add/Create File...");
				newFileItem.setIcon(Images.ADD_FILE_ICON);
				newFileItem.addActionListener(new ActionListener(){
					public void actionPerformed(ActionEvent e) {
						PwnIDE.editor.addFile(initialDirectory);
					}
				});
				menu.show(this,pt.x,pt.y);
			}
		}
	}

	public void mousePressed(MouseEvent e) {}
	public void mouseReleased(MouseEvent e) {}
	public void mouseEntered(MouseEvent e) {}
	public void mouseExited(MouseEvent e) {}

	public Triple<Directory,DefaultMutableTreeNode,ArrayList<TreeNode>> addFile(ArrayList<String> relativePath,File file) {
		Directory modelDirectory = project.getDirectory();
		DefaultMutableTreeNode treeNode = (DefaultMutableTreeNode)tree.getModel().getRoot();
		ArrayList<TreeNode> treePath = new ArrayList<TreeNode>(relativePath.size()+1);
		treePath.add(treeNode);
		while (!relativePath.isEmpty()) {
			TreeMap<String,Directory> modelDirectories = modelDirectory.getDirectories();
			if (modelDirectories.containsKey(relativePath.get(0))) {
				modelDirectory = modelDirectories.get(relativePath.get(0));
				for (int j=0;j<treeNode.getChildCount();++j) {
					if (((DefaultMutableTreeNode)treeNode.getChildAt(j)).getUserObject()==modelDirectory) {
						treeNode = (DefaultMutableTreeNode)treeNode.getChildAt(j);
						break;
					}
				}
				treePath.add(treeNode);
				relativePath.remove(0);
			}
			else {
				break;
			}
		}
		while (!relativePath.isEmpty()) {
			Directory newModelDirectory = new Directory(relativePath.get(0),"",modelDirectory,project);
			modelDirectory.add(newModelDirectory);
			modelDirectory = newModelDirectory;
			DefaultMutableTreeNode newTreeNode = new DefaultMutableTreeNode(modelDirectory,true);
			// NOTE: Using treeNode.add(newTreeNode) doesn't work.
			((DefaultTreeModel)tree.getModel()).insertNodeInto(newTreeNode,treeNode,treeNode.getChildCount());
			treeNode = newTreeNode;
			treePath.add(treeNode);
			relativePath.remove(0);
		}
		return new Triple<Directory,DefaultMutableTreeNode,ArrayList<TreeNode>>(modelDirectory,treeNode,treePath);
	}
	public void addNode(DefaultMutableTreeNode treeNode,ProjectFile codeFile,ArrayList<TreeNode> treePath) {
		DefaultMutableTreeNode newTreeNode = new DefaultMutableTreeNode(codeFile,false);
		// NOTE: Using treeNode.add(newTreeNode) doesn't work.
		((DefaultTreeModel)tree.getModel()).insertNodeInto(newTreeNode,treeNode,treeNode.getChildCount());
		tree.expandPath(new TreePath(treePath.toArray()));
		setSize(tree.getSize());
	}

	private static class TreeRenderer extends DefaultTreeCellRenderer {
		public Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) {
			String text = value.toString();
			if (text==null) {
				text = "";
			}
			Object o = ((DefaultMutableTreeNode)value).getUserObject();
			if (o instanceof Directory) {
				Directory dir = (Directory)o;
				dir.setExpanded(expanded);
				if (expanded) {
					setIcon(Images.DIRECTORY_EXPANDED_ICON);
				}
				else {
					setIcon(Images.DIRECTORY_ICON);
				}
			}
			else if (text.toLowerCase().endsWith(".asm")) {
				setIcon(Images.ASM_FILE_ICON);
			}
			else if (text.toLowerCase().endsWith(".inc")) {
				setIcon(Images.ASM_INCLUDE_FILE_ICON);
			}
			else if (((DefaultMutableTreeNode)value).isRoot()) {
				setIcon(Images.PROJECT_ICON);
			}
			else {
				setIcon(getDefaultLeafIcon());
			}
			if (selected) {
				setBackground(SELECTION_COLOUR);
			}
			else {
				setBackground(BACKGROUND_COLOUR);
			}
			setText(text);
			return this;
		}

	}
}
